#include "string.h"
#include "flash_bsp.h"
#include "main.h"

#define FMC_PAGE_SIZE ((uint16_t)0x1000U)
#define FMC_WRITE_START_ADDR ((uint32_t)0x0801F000U)

#define MAGIC_NUMBER ((uint32_t)0xDEAD12CFU)

#define BLOCK_STATUS_EMPTY ((uint32_t)0xFFFFFFFFU)
#define BLOCK_STATUS_WRITE ((uint32_t)0xFFFF0000U)
#define BLOCK_STATUS_DIRTY ((uint32_t)0x00000000U)

typedef struct
{
    uint32_t magic_number;
    uint32_t status[2];
    uint32_t data[10];
} flash_block_t;

static void fmc_flags_clear(void)
{
    fmc_flag_clear(FMC_FLAG_END);
    fmc_flag_clear(FMC_FLAG_WPERR);
    fmc_flag_clear(FMC_FLAG_PGAERR);
    fmc_flag_clear(FMC_FLAG_PGERR);
}

static void fmc_erase(void)
{
    uint32_t erase_counter;

    /* unlock the flash program/erase controller */
    fmc_unlock();

    /* clear all pending flags */
    fmc_flags_clear();
    /* erase the flash pages */
    fmc_page_erase(FMC_WRITE_START_ADDR);
    fmc_flags_clear();

    /* lock the main FMC after the erase operation */
    fmc_lock();
}

static void printBlock(flash_block_t *block)
{
    LOG_I("magic number: %x", block->magic_number);
    LOG_I("status0: %x", block->status[0]);
    LOG_I("status1: %x", block->status[1]);
    for (uint8_t i = 0; i < sizeof(block->data) / sizeof(uint32_t); i++)
    {
        LOG_I("data[%d]: %x", i, block->data[i]);
    }
}
static void fmc_program(uint32_t addr, uint32_t *data, uint32_t len)
{
    /* unlock the flash program/erase controller */
    fmc_unlock();
    /* clear all pending flags */
    fmc_flags_clear();
    /* program flash */
    uint32_t *ptr = data;
    while (len > 0)
    {
        fmc_word_program(addr, *ptr);
        fmc_flags_clear();  // 写每个word之后都需要清理下错误标志，因为有些复写错误标志，导致后面写不进去
        addr += sizeof(uint32_t);
        ptr += 1;
        len -= sizeof(uint32_t);
    }
    /* lock the main FMC after the program operation */
    fmc_lock();
}

static void fmc_read(uint32_t addr, uint32_t *data, uint32_t len)
{
    for (uint16_t i = 0; i < len / sizeof(uint32_t); i++)
    {
        data[i] = *(uint32_t *)(addr + i * 4);
    }
}

static uint8_t find_current_block_addr(uint32_t *addr, flash_block_t *block)
{
    uint32_t address = FMC_WRITE_START_ADDR;
    while (address < FMC_WRITE_START_ADDR + FMC_PAGE_SIZE)
    {
        fmc_read(address, (uint32_t *)block, sizeof(flash_block_t));
        if (block->magic_number == 0xFFFFFFFF && block->status[0] == BLOCK_STATUS_EMPTY)
        {
            *addr = address;
            return 1;
        }
        else if (block->magic_number == MAGIC_NUMBER && block->status[0] == BLOCK_STATUS_WRITE && block->status[1] == BLOCK_STATUS_EMPTY)
        {
            *addr = address;
            return 2;
        }
        else if (block->magic_number == MAGIC_NUMBER && block->status[0] == BLOCK_STATUS_WRITE && block->status[1] == BLOCK_STATUS_DIRTY)
        {
            address += sizeof(flash_block_t);
        }
    }
    return 3;
}

uint8_t readParam(uint32_t *data, uint32_t len)
{
    uint8_t status;
    uint32_t address = 0;
    flash_block_t block;
    status = find_current_block_addr(&address, &block);
    LOG_I("readParam find_current_block_addr addr %x status %x", address, status);
    printBlock(&block);
    if (status == 1 || status == 2)
    {
        memcpy(data, block.data, len);
    }
    else if (status == 3)
    {
        saveParam(block.data, sizeof(block.data));
        memcpy(data, block.data, len);
    }
    return 1;
}

void saveParam(uint32_t *data, uint32_t len)
{
    uint8_t status;
    uint32_t address = 0;
    flash_block_t block;
    status = find_current_block_addr(&address, &block);
    LOG_I("saveParam find_current_block_addr addr %x status %x", address, status);
    if (status == 1)
    {
        memcpy(block.data, data, len);
        block.magic_number = MAGIC_NUMBER;
        block.status[0] = BLOCK_STATUS_WRITE;
        block.status[1] = BLOCK_STATUS_EMPTY;
        fmc_program(address, (uint32_t *)&block, sizeof(flash_block_t));
        return;
    }
    else if (status == 2)
    {
        block.status[0] = BLOCK_STATUS_WRITE;
        block.status[1] = BLOCK_STATUS_DIRTY;
        fmc_program(address, (uint32_t *)&block, sizeof(flash_block_t));
        LOG_I("status %x fmc_program addr %x ", status, address);
        printBlock(&block);
        block.magic_number = MAGIC_NUMBER;
        block.status[0] = BLOCK_STATUS_WRITE;
        block.status[1] = BLOCK_STATUS_EMPTY;
        memcpy(block.data, data, len);
        address += sizeof(flash_block_t);
        if (address + sizeof(flash_block_t) >= FMC_WRITE_START_ADDR + FMC_PAGE_SIZE)
        {
            fmc_erase();
            address = FMC_WRITE_START_ADDR;
        }
        fmc_program(address, (uint32_t *)&block, sizeof(flash_block_t));
        LOG_I("status %x fmc_program addr %x ", status, address);
        printBlock(&block);
        return;
    }
    else if (status == 3)
    {
        fmc_erase();
        address = FMC_WRITE_START_ADDR;
        block.magic_number = MAGIC_NUMBER;
        block.status[0] = BLOCK_STATUS_WRITE;
        block.status[1] = BLOCK_STATUS_EMPTY;
        memcpy(block.data, data, len);
        fmc_program(address, (uint32_t *)&block, sizeof(flash_block_t));
    }
}

void flash_test(void)
{
    uint32_t data[1] = {0};
    fmc_read(FMC_WRITE_START_ADDR, data, sizeof(data));
    LOG_I("flash_test fmc_read data %x", data[0]);
    data[0] = 0xffff0000;
    fmc_program(FMC_WRITE_START_ADDR, data, sizeof(data));
    LOG_I("flash_test fmc_program data %x", data[0]);
    fmc_read(FMC_WRITE_START_ADDR, data, sizeof(data));
    LOG_I("flash_test fmc_read data %x", data[0]);
    data[0] = 0x00000000;
    fmc_program(FMC_WRITE_START_ADDR, data, sizeof(data));
    LOG_I("flash_test fmc_program data %x", data[0]);
    fmc_read(FMC_WRITE_START_ADDR, data, sizeof(data));
    LOG_I("flash_test fmc_read data %x", data[0]);
}